/*
 *  ClientKB.cpp
 *  GRE
 *
 *  Created by Kutlak Roman on 24/01/2012.
 *  Copyright 2012 Kutlak Roman. All rights reserved.
 *
 */

#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>

#include <iostream>
#include <sstream>

#include "ClientKB.h"

#define BUFFER_SIZE (2^20)

using std::string;
using std::cout;
using std::cerr;
using std::endl;

namespace kutlak {
namespace roman {
namespace gre {
    
    // class implemented
    /////////////////////////////// PUBLIC   ///////////////////////////////////
    
#pragma mark -
#pragma mark lifecycle
    ClientKB::ClientKB(const std::string& host) : mSocket(-1), mAddressLen(0)
    {
        // create the client socket
        mSocket = socket(AF_INET, SOCK_STREAM, 0);
        
        mAddress.sin_family = AF_INET;
        mAddress.sin_addr.s_addr = inet_addr(host.c_str());
        mAddress.sin_port = htons(9734);
        mAddressLen = sizeof(mAddress);
        
        int result = connect(mSocket, 
                             (struct sockaddr *)(&mAddress), mAddressLen);
        
        if (-1 == result)
        {
            perror("ERROR: ClientKB::ClientKB():");
            mSocket = -1;
        }
        
    }
    
    ClientKB::~ClientKB()
    {
        SendCommand(EXIT);
        close(mSocket);
    }
    
#pragma mark -
#pragma mark operations
    //=============================  OPERATIONS  ===============================
    
    std::vector<EntityPtrT> ClientKB::Entities() const
    {
        std::vector<EntityPtrT> result;
        
        SendCommand(ENTITIES);
        
        ReadEntityPtrVector(result);
        return result;
    }
    
    std::vector<EntityPtrT> ClientKB::Entities(const Property& pattern) const
    {
        std::vector<EntityPtrT> result;
        
        SendCommand(ENTITIES_WITH_PROPERTY);
        
        SendProperty(pattern);
        
        ReadEntityPtrVector(result);
        
        return result;
    }
    
    std::vector<EntityPtrT> ClientKB::EntitiesOfType(const Property& type) const
    {
        std::vector<EntityPtrT> result;
        
        SendCommand(ENTITIES_OF_TYPE);
        
        SendProperty(type);
        
        ReadEntityPtrVector(result);
        
        return result;       
    }
    
    EntityPtrT ClientKB::EntityPtrById(int entityId) const
    {
        std::vector<EntityPtrT> result;
        
        SendCommand(ENTITY_BY_ID);
        
        SendId(entityId);
        
        return ReadEntityPtr();
    }
    
    EntityPtrT ClientKB::EntityPtrByName(const std::string& name) const
    {
        std::vector<EntityPtrT> result;
        
        SendCommand(ENTITY_BY_NAME);
        
        SendString(name);
        
        return ReadEntityPtr();      
    }
    
    void ClientKB::SendCommand(ServerCommand c) const
    {
        if (-1 == mSocket)
            return;
        write(mSocket, &c, sizeof(ServerCommand));
    }
    
    void ClientKB::SendId(int id) const
    {
        if (-1 == mSocket)
            return;
        write(mSocket, &id, sizeof(id));
    }
    
    void ClientKB::SendString(const std::string& str) const
    {
        if (-1 == mSocket)
            return;
        write(mSocket, str.c_str(), str.length());
    }
    
    void ClientKB::SendProperty(const Property& p) const
    {
        if (-1 == mSocket)
            return;
        
        std::stringstream data;
        
        data << p;
        
        write(mSocket, data.str().c_str(), data.str().length());
    }
    
    EntityPtrT ClientKB::ReadEntityPtr() const
    {
        if (-1 == mSocket)
            return EntityPtrT();
        
        char buffer[BUFFER_SIZE];
        
#ifdef DEBUG
        std::cout << "Reading Entity from server.\n";
#endif
        buffer[0] = '\0';
        std::stringstream data;
        
        int bytes = read(mSocket, buffer, BUFFER_SIZE-1);    
        while (BUFFER_SIZE-1 == bytes)
        {
            buffer[bytes] = '\0';
//                std::cerr << buffer << endl;
            data << buffer;
            bytes = read(mSocket, buffer, BUFFER_SIZE-1);
        }
        
        buffer[bytes] = '\0';
        data << buffer;
            
#ifdef DEBUG
        std::cout << "ReadEntityPtr(): read " << data.str().length() << " bytes from the server\n";
            //            std::cout << data.str() << "\n\n";
#endif
            
        try
        {
            Entity e;
            data >> e;
            EntityPtrT pe = EntityPtrT(new Entity(e));
            return pe;
        }
        catch (DeserialisationException& e)
        {
            if ("NULL" == data.str())
            {
                return EntityPtrT(); // return empty pointer - result
            }
            else
            {
                std::cerr << "EXCEPTION: ClientKB::ReadEntityPtrVector(): \n\t";
                std::cerr << e.what() << std::endl;
            }
        }
        // should not get here
        return EntityPtrT();
    }
    
    void ClientKB::ReadEntityPtrVector(std::vector<EntityPtrT>& entities) const
    {
        if (-1 == mSocket)
            return;
        
        char buffer[BUFFER_SIZE];
        uint64_t numEntities = 0;
        
#ifdef DEBUG
        std::cout << "Reading data from server.\n";
#endif
        
        read(mSocket, &numEntities, sizeof(numEntities));
#ifdef DEBUG
        std::cout << numEntities << " entities to read.\n";
#endif
        buffer[0] = '\0';
        std::string data = "";
        for (ssize_t i = 0; i < numEntities; )
        {
            int bytes = read(mSocket, buffer, BUFFER_SIZE-1);

            while (BUFFER_SIZE-1 == bytes)
            {
                buffer[bytes] = '\0';
//                std::cerr << buffer << endl;
                data += buffer;
                bytes = read(mSocket, buffer, BUFFER_SIZE-1);
            }
            
            if (0 == bytes)
                break;
            
            buffer[bytes] = '\0';
            data += buffer;
            
#ifdef DEBUG
            if (i % 100 == 0)
                std::cout << "Read " << data.length() << " bytes from the server\n";
//            std::cout << data << "\n\n";
#endif
            
            size_t pos = data.rfind("</entity>\n");
            if (string::npos != pos)
            {
                std::stringstream ss;
                ss.str(data.substr(0, pos + 10));
                data = data.substr(pos + 9);
                
                try
                {
                    Entity e;
                    while (ss >> e)
                    {
                        ++i;
                        EntityPtrT pe = EntityPtrT(new Entity(e));
                        entities.push_back(pe);
                    }
                }
                catch (DeserialisationException& e)
                {
                    if (e.data() != "")
                    {
                        std::cerr << "EXCEPTION: ClientKB::ReadEntityPtrVector(): \n\t";
                        std::cerr << e.what() << std::endl;
                    }
                }
            }
        }
#ifdef DEBUG
            std::cout << "Parsed " << entities.size() << " entities.\n";
#endif
    }
    
    size_t ClientKB::Size() const
    {
        return -1;
    }
    
} // end namespace GRE
}
}
